﻿using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.DependencyInjection;

namespace Xms.Abp.Features;

public class FeaturesDefinitionManager : IFeaturesDefinitionManager, ISingletonDependency
{
    protected IDictionary<string, FeaturesGroupDefinition> FeaturesGroupDefinitions => _lazyFeaturesGroupDefinitions.Value;
    private readonly Lazy<Dictionary<string, FeaturesGroupDefinition>> _lazyFeaturesGroupDefinitions;

    protected IDictionary<string, FeatureValueDefinition> FeatureValueDefinitions => _lazyFeatureValueDefinitions.Value;
    private readonly Lazy<Dictionary<string, FeatureValueDefinition>> _lazyFeatureValueDefinitions;

    protected FeatureOptions Options { get; }

    private readonly IServiceScopeFactory _serviceScopeFactory;

    public FeaturesDefinitionManager(
        IOptions<FeatureOptions> options,
        IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
        Options = options.Value;

        _lazyFeatureValueDefinitions = new Lazy<Dictionary<string, FeatureValueDefinition>>(
            CreateFeatureDefinitions,
            isThreadSafe: true
        );

        _lazyFeaturesGroupDefinitions = new Lazy<Dictionary<string, FeaturesGroupDefinition>>(
            CreateFeaturesGroupDefinitions,
            isThreadSafe: true
        );
    }

    public virtual FeatureValueDefinition Get(string name)
    {
        Check.NotNull(name, nameof(name));

        var feature = GetOrNull(name);

        if (feature == null)
        {
            throw new AbpException("Undefined feature: " + name);
        }

        return feature;
    }

    public virtual IReadOnlyList<FeatureValueDefinition> GetAll()
    {
        return FeatureValueDefinitions.Values.ToImmutableList();
    }

    public virtual FeatureValueDefinition GetOrNull(string name)
    {
        return FeatureValueDefinitions.GetOrDefault(name);
    }

    public IReadOnlyList<FeaturesGroupDefinition> GetGroups()
    {
        return FeaturesGroupDefinitions.Values.ToImmutableList();
    }

    protected virtual Dictionary<string, FeatureValueDefinition> CreateFeatureDefinitions()
    {
        var features = new Dictionary<string, FeatureValueDefinition>();

        foreach (var groupDefinition in FeaturesGroupDefinitions.Values)
        {
            foreach (var feature in groupDefinition.Features)
            {
                AddFeatureToDictionaryRecursively(features, feature);
            }
        }

        return features;
    }

    protected virtual void AddFeatureToDictionaryRecursively(
        Dictionary<string, FeatureValueDefinition> features,
        FeatureValueDefinition feature)
    {
        if (features.ContainsKey(feature.Name))
        {
            throw new AbpException("Duplicate feature name: " + feature.Name);
        }

        features[feature.Name] = feature;

        foreach (var child in feature.Children)
        {
            AddFeatureToDictionaryRecursively(features, child);
        }
    }

    protected virtual Dictionary<string, FeaturesGroupDefinition> CreateFeaturesGroupDefinitions()
    {
        var context = new FeaturesDefinitionContext();

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var providers = Options
                .DefinitionProviders
                .Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeaturesDefinitionProvider)
                .ToList();

            foreach (var provider in providers)
            {
                provider.Define(context);
            }
        }

        return context.Groups;
    }
}
